package network;

import java.io.File;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Observable;
import java.util.Observer;
import java.util.logging.Level;
import java.util.logging.Logger;
import messages.*;
import modelcontroller.ChatController;
import modelcontroller.ChatModel;

/**
 *
 * @author Mariam
 */
public class ChatNI implements Observer, RemoteApp {

    /**
     * The broadcast address used to send a UDP message to all of the machines
     * of a same LAN
     *
     * {@value #ADDR_BROADCAST}
     */
    protected static final String ADDR_BROADCAST = "255.255.255.255";
    /**
     * The listening port in UDP
     */
    protected int COMMON_PORT_LISTEN_UDP;
    /**
     * The listening port in TCP
     */
    protected int COMMON_PORT_LISTEN_TCP;
    /**
     * The destination port in UDP
     */
    protected int COMMON_PORT_SEND_UDP;
    /**
     * The destination port in TCP
     */
    protected int COMMON_PORT_SEND_TCP;
    private DatagramSocket socketSendUDP;
    private DatagramSocket socketReceiveUDP;
    private Socket socketSendTCP;
    private ServerSocket socketReceiveTCP;
    /**
     * The model associated to the network interface
     */
    protected ChatModel model;
    /**
     * The controller associated to the network interface
     */
    protected ChatController controller;
    private ThreadSendMsg threadSendMsg;
    private ThreadReceiveMsg threadReceiveMsg;
    private ThreadSendFile threadSendFile;
    private ThreadReceiveFile threadReceiveFile;

    /**
     * Constructs a network interface.
     * <p>
     * Usually, portSendMsg = portReceiveMsg and portSendFile = portReceiveFile
     * but if we want to run the application twice on the same machine, we need
     * to cross the ports numbers.
     *
     * @param model the model associated to the view NI
     * @param portSendMsg the destination port in UDP
     * @param portSendFile the destination port in TCP
     * @param portReceiveMsg the listening port in UDP
     * @param portReceiveFile the listening port in TCP
     */
    public ChatNI(ChatModel model, int portSendMsg, int portSendFile, int portReceiveMsg, int portReceiveFile) {
        COMMON_PORT_LISTEN_UDP = portSendMsg;
        COMMON_PORT_LISTEN_TCP = portSendFile;
        COMMON_PORT_SEND_UDP = portReceiveMsg;
        COMMON_PORT_SEND_TCP = portReceiveFile;
        this.model = model;
    }

    /**
     * Sets the controller associated to the netwerk interface
     *
     * @param controller
     */
    public void setController(ChatController controller) {
        this.controller = controller;
    }

    /**
     * Creates the sockets needed to send and receive
     */
    public void start() {
        try {
            this.socketSendUDP = new DatagramSocket();
            this.socketReceiveUDP = new DatagramSocket(COMMON_PORT_LISTEN_UDP);
            this.socketSendTCP = new Socket();
            this.socketReceiveTCP = new ServerSocket(COMMON_PORT_LISTEN_TCP);
        } catch (SocketException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Closes all of the sockets that have been used to communicate
     */
    public void stop() {
        try {
            socketReceiveTCP.close();
            socketReceiveUDP.close();
            socketSendTCP.close();
            socketSendUDP.close();
        } catch (IOException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Sends a broadcast hello message to all of the connected users to announce
     * the arrival (connection) of the local user
     *
     * @param username the login of the local user
     */
    @Override
    public void sendHello(String username) {
        try {
            //System.out.println("Sending of a HELLO message");
            threadSendMsg = new ThreadSendMsg(socketSendUDP, InetAddress.getByName(ADDR_BROADCAST), MessagesFactory.getHello(username, true), this);
            threadSendMsg.start();
        } catch (MessageCreationException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Sends a hello back to acknowledge the hello lessage sent by the remote
     * user and to inform him about the presence of the local user
     *
     * @param remoteUsername the login of the remote user who has sent a hello
     * message to the local user
     */
    @Override
    public void sendHelloAck(String remoteUsername) {
        try {
            //System.out.println("Sending of a HELLO ACK message");
            threadSendMsg = new ThreadSendMsg(socketSendUDP, InetAddress.getByName(remoteUsername), MessagesFactory.getHello(model.getLocalUser().getUsername(), false), this);//InetAddress.getLocalHost().getHostName(), true));
            threadSendMsg.start();
        } catch (MessageCreationException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Sends a bye message in broadcast to informs other remote connected users
     * about the disconnection of the local user
     */
    @Override
    public void sendBye() {
        try {
            //System.out.println("Sending of a BYE message");
            threadSendMsg = new ThreadSendMsg(socketSendUDP, InetAddress.getByName(ADDR_BROADCAST), MessagesFactory.getBye(model.getLocalUser().getUsername()), this);
            threadSendMsg.start();
            threadSendMsg.join();
            threadReceiveMsg.kill();
            stop();
        } catch (InterruptedException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MessageCreationException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * Sends a text message to a remote user
     *
     * @param username the login of the receiver (remote user)
     * @param msgToSend the message that has been typed by the local user
     */
    @Override
    public void sendMsg(String username, String msgToSend) {
        try {
            String hostname = model.getHashmapUsers().get(username).getHostname();
            //System.out.println("Sending of a CHAT message");
            threadSendMsg = new ThreadSendMsg(socketSendUDP, InetAddress.getByName(hostname), MessagesFactory.getChatMessage(model.getLocalUser().getUsername(), msgToSend), this);
            threadSendMsg.start();
        } catch (MessageCreationException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Sends a request message to ask the remote user if he wants to download a
     * file sent be the local user
     *
     * @param username the login of the receiver (remote user)
     * @param filename the name of the file the local user wants to send
     * @param size the size of the file
     */
    @Override
    public void sendFileRequest(String username, String filename, long size) {
        //System.out.println("Sending of a FILE NOTIF message");
        try {
            String hostname = model.getHashmapUsers().get(username).getHostname();
            threadSendMsg = new ThreadSendMsg(socketSendUDP, InetAddress.getByName(hostname), MessagesFactory.getFileNotif(model.getLocalUser().getUsername(), filename, true, false, size), this);
            threadSendMsg.start();
        } catch (MessageCreationException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Sends the local user's reply to a file request sent by a remote user
     *
     * @param username the login of the receiver (remote user)
     * @param filename the name of the file the remote user has proposed to send
     * to the local user
     * @param accepted the choice of the local user : if true he wants to
     * download the file and waits for the sending, if false he refuses the
     * sending of the file
     * @param size the size of the file
     */
    @Override
    public void sendFileAnswer(String username, String filename, boolean accepted, long size) {
        //System.out.println("Sending of a FILE ANSWER message : " + accepted);
        try {
            String hostname = model.getHashmapUsers().get(username).getHostname();
            threadSendMsg = new ThreadSendMsg(socketSendUDP, InetAddress.getByName(hostname), MessagesFactory.getFileNotif(model.getLocalUser().getUsername(), filename, false, accepted, size), this);
            threadSendMsg.start();
        } catch (MessageCreationException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Sends the file to the remote user
     *
     * @param username the login of the receiver (remote user)
     * @param filepath the path where is located the file to send
     */
    @Override
    public void sendFile(String username, String filepath) {
        try {
            File file = new File(filepath);
            String hostname = model.getHashmapUsers().get(username).getHostname();
            threadSendFile = new ThreadSendFile(socketSendTCP, InetAddress.getByName(hostname), file, this);
            threadSendFile.start();
        } catch (UnknownHostException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Handles the reception of a hello message
     *
     * @param username the login of the receiver (remote user)
     * @param hostname the name of the the remote user's machine
     */
    @Override
    public void helloRcvd(String username, String hostname) {
        controller.performHelloRcvd(username, hostname);
    }

    /**
     * Handles the reception of a bye message
     *
     * @param username the login of the receiver (remote user)
     */
    @Override
    public void byeRcvd(String username) {
        controller.performByeRcvd(model.getHashmapUsers().get(username));
    }

    /**
     * Handles the reception of a text message
     *
     * @param username the login of the receiver (remote user)
     * @param msg the text message that has been received
     */
    @Override
    public void msgRcvd(String username, String msg) {
        controller.performMsgRcvd(model.getHashmapUsers().get(username), msg);
    }

    /**
     * Handles the reception of a file request
     *
     * @param username the login of the receiver (remote user)
     * @param filename the name of the file the remote user wants to send
     * @param size the size of the file
     */
    @Override
    public void fileRequestRcvd(String username, String filename, long size) {
        controller.performFileRequestRcvd(username, filename, size);
    }

    /**
     * Handles the reception of a file answer
     *
     * @param username the login of the receiver (remote user)
     * @param filename
     * @param accepted
     */
    @Override
    public void fileAnswerRcvd(String username, String filename, boolean accepted) {
        controller.performFileAnswerRcvd(username, filename, accepted);
    }

    /**
     * Starts the thread of reception in UDP to listen to incoming messages
     */
    @Override
    public void receive() {
        threadReceiveMsg = new ThreadReceiveMsg(socketReceiveUDP, this);
        threadReceiveMsg.start();
    }

    /**
     * Start the thread of reception in TCP to retrieve the file sent by a
     * remote user
     *
     * @param filename the name of the file previously indicated in the file
     * request
     * @param filepath the final destination where the file will be saved
     */
    @Override
    public void receiveFile(String filename, String filepath) {
        try {
            File file = new File(filepath + File.separatorChar + filename);
            file.createNewFile();
            threadReceiveFile = new ThreadReceiveFile(socketReceiveTCP, this, file);
            threadReceiveFile.start();
        } catch (IOException ex) {
            Logger.getLogger(ChatNI.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    @Override
    public void update(Observable o, Object arg) {
        // System.out.println("UPDATE");
        if (arg.equals(ChatModel.ValueUpdated.STATE)) {
            // we send HELLO
            if (model.getLocalUserIsConnected()) {
                this.start();
                receive();
                sendHello(model.getLocalUser().getUsername());
            } else { // we send BYE
                sendBye();
                //getThreadReceiveMsg().kill(); 

                //getThreadReceiveMsg().interrupt();              
                //this.stop();
            }
        }
    }
}
